package com.jun.admin.core.config;

import com.alibaba.druid.DbType;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import com.alibaba.druid.support.spring.stat.DruidStatInterceptor;
import com.alibaba.druid.util.Utils;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.JdkRegexpMethodPointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.servlet.*;
import javax.sql.DataSource;
import java.io.IOException;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

/**
 * @author yuejun.li
 * @date 2024/7/9 10:49:43
 * 数据库配置类
 */
@Configuration
public class DataSourceConfig {

    /**
     * 配置数据源：【将所有前缀为spring.datasource.druid下的配置项都加载到DataSource中】
     */
    @Bean("dataSource")
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource createDBDataSource() throws SQLException {
        //druid数据源
        DruidDataSource druidDataSource = new DruidDataSource();
        //初始化连接数
        druidDataSource.setInitialSize(5);
        //最小空闲连接数
        druidDataSource.setMinIdle(10);
        //最大活跃连接数
        druidDataSource.setMaxActive(300);
        //获取连接的最大等待时间（毫秒）
        druidDataSource.setMaxWait(30000);
        //连接回收的时间间隔（毫秒）
        druidDataSource.setTimeBetweenEvictionRunsMillis(60000);
        //连接在池中保持空闲的最小时间（毫秒）
        druidDataSource.setMinEvictableIdleTimeMillis(300000);
        //验证连接是否有效的SQL语句
        druidDataSource.setValidationQuery("SELECT 1 FROM DUAL");
        //当连接空闲时，是否进行有效性检测
        druidDataSource.setTestWhileIdle(true);
        //当从连接池借用连接时，是否进行有效性检测
        druidDataSource.setTestOnBorrow(false);
        //当归还连接时，是否进行有效性检测
        druidDataSource.setTestOnReturn(false);
        //是否使用预处理语句
        druidDataSource.setPoolPreparedStatements(true);
        //每个连接最多可以创建的预处理语句数量
        druidDataSource.setMaxPoolPreparedStatementPerConnectionSize(20);
        //重试次数
        druidDataSource.setConnectionErrorRetryAttempts(1);
        //是否使用全局数据源统计信息（监控多个数据源）
        druidDataSource.setUseGlobalDataSourceStat(true);
        //属性类型是字符串，通过别名的方式配置扩展插件
        //常用的插件有：监控统计用的filter:stat, 日志用的filter:log4j, 防御sql注入的filter:wall
        druidDataSource.setFilters("stat,wall");
        //连接属性配置
        Properties properties = new Properties();
        properties.setProperty("druid.stat.mergeSql", "true");
        //慢sql检测超过便在监测页面报红，value为用时(ms)
        properties.setProperty("druid.stat.slowSqlMillis", "300");
        druidDataSource.setConnectProperties(properties);
        //数据库类型
        druidDataSource.setDbType(DbType.mysql);
        return druidDataSource;
    }

    /**
     * 配置Druid监控
     */
    @Bean
    public ServletRegistrationBean druidServlet(){
        //注册服务
        ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean();
        servletRegistrationBean.setServlet(new StatViewServlet());
        servletRegistrationBean.setUrlMappings(Arrays.asList("/druid/*"));
        //IP白名单(为空表示所有都可以访问，多个IP用逗号隔开)
        servletRegistrationBean.addInitParameter("allow","127.0.0.1");
        //IP黑名单(deny优先于allow)
        servletRegistrationBean.addInitParameter("deny","127.0.0.2");
        //设置控制台账号密码
        servletRegistrationBean.addInitParameter("loginUsername","admin");
        servletRegistrationBean.addInitParameter("loginPassword","123456");
        //是否能够重置数据
        servletRegistrationBean.addInitParameter("resetEnable","false");
        return servletRegistrationBean;
    }

    /**
     * 配置web监控的filter
     */
    @Bean
    public FilterRegistrationBean webStatFilter(){
        //注册服务
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
        filterRegistrationBean.setFilter(new WebStatFilter());
        //添加过滤规则
        Map<String,String> initParams = new HashMap<>(1);
        //设置忽略请求
        initParams.put("exclusions","*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*");
        filterRegistrationBean.setInitParameters(initParams);
        filterRegistrationBean.addInitParameter("profileEnable","true");
        filterRegistrationBean.addInitParameter("aopPatterns","");
        //验证所有请求
        filterRegistrationBean.addUrlPatterns("/*");
        return filterRegistrationBean;
    }

    /**
     * 配置事务管理器
     */
    @Bean("transactionManager")
    public DataSourceTransactionManager createTransactionManager() throws SQLException {
        return new DataSourceTransactionManager(createDBDataSource());
    }


    /**
     * Spring监控配置 【基于AOP实现】
     */
    @Bean
    public DruidStatInterceptor druidStatInterceptor() {
        return new DruidStatInterceptor();
    }

    @Bean
    @Scope("prototype")
    public JdkRegexpMethodPointcut druidStatPointcut() {
        JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut();
        //监控包名
        pointcut.setPattern("com.jun.admin.core.controller.*");
        //不监控包名
        //pointcut.setExcludedPatterns("");
        return pointcut;
    }

    @Bean
    public DefaultPointcutAdvisor druidStatAdvisor(DruidStatInterceptor druidStatInterceptor, JdkRegexpMethodPointcut druidStatPointcut) {
        DefaultPointcutAdvisor defaultPointAdvisor = new DefaultPointcutAdvisor();
        defaultPointAdvisor.setPointcut(druidStatPointcut);
        defaultPointAdvisor.setAdvice(druidStatInterceptor);
        return defaultPointAdvisor;
    }

    /**
     * 去底部广告
     */
    @Bean
    public FilterRegistrationBean removeDruidAdFilterRegistrationBean(DruidStatProperties properties) {

        // 获取web监控页面的参数
        DruidStatProperties.StatViewServlet config = properties.getStatViewServlet();
        // 提取common.js的配置路径
        String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*";
        String commonJsPattern = pattern.replaceAll("\\*", "js/common.js");

        final String filePath = "support/http/resources/js/common.js";

        //创建filter进行过滤
        Filter filter = new Filter() {
            @Override
            public void init(FilterConfig filterConfig) throws ServletException {}

            @Override
            public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
                chain.doFilter(request, response);
                // 重置缓冲区，响应头不会被重置
                response.resetBuffer();
                // 获取common.js
                String text = Utils.readFromResource(filePath);
                // 正则替换banner, 除去底部的广告信息
                text = text.replaceAll("<a.*?banner\"></a><br/>", "");
                text = text.replaceAll("powered.*?shrek.wang</a>", "");
                response.getWriter().write(text);
            }
            @Override
            public void destroy() {}
        };
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        registrationBean.setFilter(filter);
        registrationBean.addUrlPatterns(commonJsPattern);
        return registrationBean;
    }
}
